package com.demon.framework.common.aspect;

import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSON;
import com.demon.framework.util.net.IpUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.time.Instant;
import java.util.*;

/**
 * MVC 日志切面 记录
 *
 * @author Demon-HY
 * @date 2020-7-23
 */
@Aspect
@Slf4j
public class LogAroundAop {

    /**
     * mvc 出参打印的最大长度字符数
     */
    @Value("${demon.mvc.print.return.limit:512}")
    private Integer retStrLimit;

    /**
     * 记录请求时间
     */
    private static final ThreadLocal<Long> REQUEST_TIME = new ThreadLocal<>();

    private static void setRequestTime(Long requestTime) {
        REQUEST_TIME.set(requestTime);
    }

    private static Long getRequestTime() {
        return REQUEST_TIME.get();
    }

    /**
     * 清除本地线程的数据
     */
    private static void removeThreadLocal() {
        REQUEST_TIME.remove();
        MDC.clear();
    }

    /**
     * 在Controller 加日志切面，{@link com.demon.framework.common.annotation.InvokingLogger}注解
     */
    @Pointcut("@annotation(com.demon.framework.common.annotation.InvokingLogger)")
    public void logAround() {
    }

    @Before("logAround()")
    public void doBefore(JoinPoint joinPoint) {
        // 记录请求时间
        setRequestTime(System.currentTimeMillis());

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            return;
        }
        HttpServletRequest req = attributes.getRequest();

        Map<String, Object> requestParams = getRequestParams(joinPoint, req);

        log.info("请求入参= X-IP:{} X-Uri:{} X-Header:{} X-Param:{}",
                IpUtils.getIpAddr(req), getRequestUrl(req),
                getRequestHeader(req), JSON.toJSONString(requestParams));
    }

    @AfterReturning(value = "logAround()", returning = "result")
    public void doAfterReturning(JoinPoint joinPoint, Object result) {
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes == null) {
                return;
            }
            HttpServletRequest req = attributes.getRequest();

            // 处理完请求，返回内容
            log.info("请求出参= X-IP:{} X-Uri:{} X-Result:{} X-Time:{}",
                    IpUtils.getIpAddr(req), getRequestUrl(req), getResponse(result), getRequestTaking());
        } finally {
            removeThreadLocal();
        }
    }

    @AfterThrowing(value = "logAround()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes == null) {
                return;
            }
            HttpServletRequest req = attributes.getRequest();

            // 处理完请求，返回内容
            log.error("请求出参= X-IP:{} X-Uri:{} X-Error:{} X-Time:{}",
                    IpUtils.getIpAddr(req), getRequestUrl(req), e.getMessage(), getRequestTaking());
        } finally {
            removeThreadLocal();
        }
    }

    /**
     * 获取请求路径
     */
    private String getRequestUrl(HttpServletRequest req) {
        return req.getRequestURI();
    }

    /**
     * 获取请求头
     */
    private Map<String, List<String>> getRequestHeader(HttpServletRequest req) {
        // 需要保存的header头
        List<String> logHeaderNames = Arrays.asList("referer", "cookie", "token", "origin", "request-origion", "authorization",
                "host", "x-requested-with", "user-agent");

        Map<String, List<String>> headers = new HashMap<>(16);
        Enumeration<String> headerNames = req.getHeaderNames();
        if (headerNames != null) {
            while (headerNames.hasMoreElements()) {
                String name = headerNames.nextElement();
                // 不需要记录的信息直接跳过
                if (name == null || !logHeaderNames.contains(name.toLowerCase())) {
                    continue;
                }
                String value = req.getHeader(name);

                if (!name.isEmpty() && value != null) {
                    List<String> valueList = new ArrayList<>();
                    if (headers.containsKey(name)) {
                        headers.get(name).add(value);
                    }
                    valueList.add(value);
                    headers.put(name, valueList);
                }
            }
        }
        return Collections.unmodifiableMap(headers);
    }

    /**
     * 获取请求参数
     */
    private Map<String, Object> getRequestParams(JoinPoint joinPoint, HttpServletRequest request) {
        String method = request.getMethod();
        Object[] args = joinPoint.getArgs();
        Map<String, Object> params = new HashMap<>(20);
        // 获取请求参数集合并进行遍历拼接
        try {
            if (args.length > 0) {
                String queryString = request.getQueryString();

                if (HttpPost.METHOD_NAME.equals(method)) {
                    Object object = args[0];
                    params = BeanUtil.beanToMap(object, false, true);
                    if (StringUtils.isNotBlank(queryString)) {
                        params.putAll(paramToMap(queryString));
                    }
                } else if (HttpGet.METHOD_NAME.equals(method)) {
                    params = paramToMap(queryString);
                }
            }
        } catch (Exception ignored) {
        }

        return params;
    }

    /**
     * 获取返回结果
     */
    private String getResponse(Object result) {
        String resultObj = "";
        try {
            if (result instanceof String) {
                return (String) result;
            }

            resultObj = JSON.toJSONString(result);
            resultObj = resultObj.length() > retStrLimit ? resultObj.substring(0, retStrLimit - 1) : resultObj;
        } catch (Exception ignored) {
        }

        return resultObj;
    }

    /**
     * 获取请求耗时,单位毫秒
     */
    private Long getRequestTaking() {
        Long endTime = Instant.now().toEpochMilli();
        return endTime - getRequestTime();
    }

    /**
     * 请求GET参数转Map
     *
     * @param queryString param1=xxx&param2=xxx&param3=xxx
     */
    public static Map<String, Object> paramToMap(String queryString) {
        Map<String, Object> param = new HashMap<>(8);

        try {
            if (StringUtils.isBlank(queryString)) {
                return param;
            }

            for (String query : queryString.split("&")) {
                String[] queryParam = query.split("=");
                if (queryParam.length == 2) {
                    param.put(queryParam[0], queryParam[1]);
                } else {
                    param.put(queryParam[0], "");
                }
            }
        } catch (Exception ignored) {
        }

        return param;
    }
}
